package storager

import (
	"context"
	"errors"
	"gf_blog_admin/internal/consts"
	
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gcache"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/gogf/gf/v2/util/grand"
	"sort"
	"strconv"
	"strings"
	"time"
)

type (
	// Multipart 本地分片上传
	Multipart struct {
		config *LocalConfig
	}
	// MultipartProgress 分片进度
	MultipartProgress struct {
		UploadId             string                   `json:"uploadId"`             // 上传事件ID
		PartNumberMarker     int64                    `json:"partNumberMarker"`     // 起始位置
		NextPartNumberMarker int64                    `json:"nextPartNumberMarker"` // 下次起始位置
		ShardCount           int64                    `json:"shardCount"`           // 分片数量
		Meta                 *FileMeta                `json:"meta"`                 // 文件元数据
		Parts                map[int64]*MultipartItem `json:"Parts"`                // 已上传的分片
		CreatedAt            time.Time                `json:"createdAt"`            // 创建时间
		ExpireAt             time.Time                `json:"expireAt"`             // 过期时间
	}
	// UploadPartParams 分片上传
	UploadPartParams struct {
		UploadId   string            `json:"uploadId"`         // 上传事件ID
		PartNumber int64             `json:"partNumber"`       // 分片索引
		File       *ghttp.UploadFile `json:"file" type:"file"` // 分片文件
		MP         *MultipartProgress
	}
	// CheckMultipartParams 检查文件分片
	CheckMultipartParams struct {
		UploadId string           `json:"uploadId"` //  上传事件ID
		Parts    []*MultipartItem `json:"parts"`    //  已上传的分片
		FileName string           `json:"fileName"` //  文件名
		MimeType string           `json:"mimeType"` //  mimetype
	}
	// MultipartItem 分片信息
	MultipartItem struct {
		PartNumber   int64     `json:"partNumber"`   // 分片编码
		LastModified time.Time `json:"lastModified"` // 上传时间
		ETag         string    `json:"etag"`         // 唯一标识
		Size         int64     `json:"size"`         // 分片大小
	}
)

func NewMultipart(config *LocalConfig) *Multipart {
	if config == nil {
		config = &LocalConfig{}
	}
	if config.Path == "" {
		config.Path = "temp/uploads"
	}
	return &Multipart{config}
}

// InitiateMultipart 创建分片事件
func (m *Multipart) InitiateMultipart(ctx context.Context) (mp *MultipartProgress, err error) {
	nowTime := time.Now()
	mp = &MultipartProgress{
		UploadId:             grand.S(32),
		PartNumberMarker:     0,
		NextPartNumberMarker: 0,
		ShardCount:           0,
		Parts:                make(map[int64]*MultipartItem),
		CreatedAt:            nowTime,
		ExpireAt:             nowTime.Add(TaskExpireIn),
	}
	err = m.addMultipartCache(ctx, mp)
	if err != nil {
		return nil, err
	}
	return
}

// UploadPart 上传分片
func (m *Multipart) UploadPart(ctx context.Context, in *UploadPartParams) (part *MultipartItem, err error) {
	var (
		tempDir = m.partTempDir(in.UploadId)
		nowTime = time.Now()
	)
	in.MP, err = m.getMultipartCache(ctx, in.UploadId)
	if err != nil {
		return nil, err
	}
	part = &MultipartItem{
		PartNumber:   in.PartNumber,
		LastModified: nowTime,
		Size:         in.File.Size,
	}
	part.ETag, err = CalcUploadFileMd5(in.File)
	if err != nil {
		return nil, err
	}
	if err = hasContentMd5(ctx, part.ETag); err != nil {
		return nil, err
	}
	// 写入文件
	in.File.Filename = strconv.FormatInt(in.PartNumber, 10)
	if _, err = in.File.Save(tempDir, false); err != nil {
		return nil, err
	}
	// 更新上传进度
	in.MP.Parts[in.PartNumber] = part
	if in.MP.PartNumberMarker == 0 {
		in.MP.PartNumberMarker = in.PartNumber
	}
	in.MP.NextPartNumberMarker = in.PartNumber
	err = m.upMultipartCache(ctx, in.MP)
	return
}

// CompleteMultipart 完成上传分片
func (m *Multipart) CompleteMultipart(ctx context.Context, in *CheckMultipartParams) (obj *FileObject, err error) {
	mp, err := m.getMultipartCache(ctx, in.UploadId)
	if err != nil {
		return nil, err
	}
	var (
		lNum = len(mp.Parts)
		rNum = len(in.Parts)
	)
	if lNum != rNum {
		return nil, errors.New("缺少分片文件")
	}
	// 合并文件
	obj, err = m.mergePartFile(mp, in.Parts)
	if err != nil {
		return nil, errors.New("合并分片文件出错")
	}
	// 删除任务
	_ = m.AbortMultipart(ctx, in.UploadId)
	return
}

// AbortMultipart 删除分片任务
func (m *Multipart) AbortMultipart(ctx context.Context, uploadId string) (err error) {
	tempDir := m.partTempDir(uploadId)
	// 删除临时分片
	if err = gfile.RemoveAll(tempDir); err != nil {
		return errors.New("删除临时分片文件出错")
	}
	// 删除缓存
	if err = m.delMultipartCache(ctx, uploadId); err != nil {
		return errors.New("删除上传事件任务出错")
	}
	return
}

// ListParts 列举已上传分片
func (m *Multipart) ListParts(ctx context.Context, uploadId string) (mp *MultipartProgress, err error) {
	mp, err = m.getMultipartCache(ctx, uploadId)
	return
}

// 合并分片文件
func (m *Multipart) mergePartFile(mp *MultipartProgress, parts []*MultipartItem) (obj *FileObject, err error) {
	var (
		fileSize int64
		fileName = m.customFileName()
		tempDir  = m.partTempDir(mp.UploadId)
		nowDate  = time.Now().Format("2006-01-02")
	)
	tempPath := gfile.Join(tempDir, fileName)
	defer func() {
		_ = gfile.RemoveAll(tempPath)
	}()
	// 重新排序
	sort.Slice(parts, func(i, j int) bool {
		return parts[i].PartNumber < parts[j].PartNumber
	})
	// 进行合并
	for _, part := range parts {
		chunkPath := gfile.Join(tempDir, strconv.FormatInt(part.PartNumber, 10))
		if !gfile.Exists(chunkPath) {
			return nil, errors.New("分片文件出现缺失")
		}
		if mp.Parts[part.PartNumber].ETag != part.ETag {
			return nil, errors.New("验证分片文件hash不一致")
		}
		if err = gfile.PutBytesAppend(tempPath, gfile.GetBytes(chunkPath)); err != nil {
			return nil, err
		}
		fileSize += part.Size
	}
	// 获取文件信息
	info, err := FileInfo(tempPath)
	if err != nil {
		return nil, errors.New("获取文件信息失败")
	}
	// 存储路径
	key := strings.Join([]string{nowDate, info.Md5 + info.Ext}, "/")
	// 移动到存储路径
	if err = gfile.CopyFile(tempPath, gfile.Join(m.config.Path, key)); err != nil {
		return nil, err
	}
	obj = &FileObject{
		Key:  "uploads/" + key,
		Hash: "",
		Meta: &FileMeta{
			Filename: fileName + info.Ext,
			Size:     fileSize,
			Kind:     info.Kind,
			MimeType: info.MimeType,
			Ext:      info.Ext,
			Md5:      info.Md5,
		},
	}
	return
}

// 分片文件临时存放路径
func (m *Multipart) partTempDir(uploadId string) string {
	return gfile.Join(gfile.Temp("gf_upload_temp"), uploadId)
}

// 缓存key
func (m *Multipart) cacheKey(ctx context.Context, uploadId string) string {
	uid := ctx.Value(consts.CtxUId).(uint64)
	return strings.Join([]string{CacheMPUpload, strconv.FormatUint(uid, 10), uploadId}, ":")
}

// 分片事件信息
func (m *Multipart) getMultipartCache(ctx context.Context, uploadId string) (mp *MultipartProgress, err error) {
	key := m.cacheKey(ctx, uploadId)
	get, err := gcache.Get(ctx, key)
	if err != nil {
		return nil, err
	}
	if get == nil {
		return nil, errors.New("分片事件不存在，请重新上传")
	}
	err = get.Scan(&mp)
	return
}

// 创建分片事件缓存
func (m *Multipart) addMultipartCache(ctx context.Context, in *MultipartProgress) (err error) {
	key := m.cacheKey(ctx, in.UploadId)
	expireIn := time.Duration(in.ExpireAt.Unix() - time.Now().Unix())
	return gcache.Set(ctx, key, in, expireIn*time.Second)
}

// 更新分片事件缓存
func (m *Multipart) upMultipartCache(ctx context.Context, in *MultipartProgress) (err error) {
	key := m.cacheKey(ctx, in.UploadId)
	expireIn := time.Duration(in.ExpireAt.Unix() - time.Now().Unix())
	return gcache.Set(ctx, key, in, expireIn*time.Second)
}

// 删除分片事件缓存
func (m *Multipart) delMultipartCache(ctx context.Context, uploadId string) (err error) {
	key := m.cacheKey(ctx, uploadId)
	_, err = gcache.Remove(ctx, key)
	return
}

// 自定义文件名
func (m *Multipart) customFileName(ext ...string) string {
	var (
		t          = time.Now()
		nano int64 = 0
	)
	if !t.IsZero() {
		nano = t.UnixNano()
	}
	name := strings.ToLower(strconv.FormatInt(nano, 36) + grand.S(6))
	if len(ext) > 0 && ext[0] != "" {
		name = name + "." + ExtName(ext[0])
	}
	return name
}
